﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.IO;

namespace TCG
{
    /// <summary>
    /// 资源数据库的基类。游戏中所有的资源都是按照 {id}-{path} 的映射形式组织的。数据库在初始化时，
    /// 要从磁盘读取映射表。
    /// </summary>
    public abstract class BaseAssetsDatabase<T> : MonoBehaviour where T : class
	{
		/// <summary>
		/// 初始化数据库，读取{id}-{path}映射表。
		/// </summary>
        
        public void Initialize (GameAssetsLoader assetLoader)
        {
            m_assetLoader = assetLoader;
			FileStream fs = new FileStream (GetMapFilePath (), FileMode.Open);
			AssetIdPathMap idPathMap = ProtoBuf.Serializer.Deserialize<AssetIdPathMap> (fs);
			m_assetIdPathTable = idPathMap.idPathMap;
			fs.Close ();
        }

		/// <summary>
		/// 处理加载请求。
		/// </summary>

        public void LoadAsset (AssetRequest request)
        {
			request.assetUrl = CommonTools.GetStreamingPath (GetAssetPath (request.assetId));
			Debug.Log ("收到加载资源请求：" + request.assetUrl);

			// 检查资源是否已存在
			if (m_cachedAssets.ContainsKey (request.assetId))
			{
				Debug.Log ("要加载的资源已在缓存中：" + request.assetUrl);
				StartCoroutine (Co_NotifyLoadSucc (request, m_cachedAssets[request.assetId]));
				return;
			}

			// 检查同一资源是否正在加载
			IList<AssetRequest> requests = null;
			if (m_waitingRequests.ContainsKey (request.assetId))
			{
				requests = m_waitingRequests[request.assetId];
			}
			else
			{
				requests = new List<AssetRequest> ();
				m_waitingRequests.Add (request.assetId, requests);
			}

			if (requests.Count > 0)
			{
				Debug.Log ("要加载的资源有多份请求，加入等待队列：" + request.assetUrl);
				requests.Add (request);
			}
			else
			{
				requests.Add (request);
				DoLoad (request);
			}
        }

		/// <summary>
		/// 释放资源，会将其引用计数减1。引用计数为0的资源会在外部请求时优先释放。
		/// </summary>

		public void ReleaseAsset (GameAsset<T> asset)
		{
			asset.DecRef ();
			if (asset.refCount <= 0)
			{
				m_zeroRefAssets.Add (asset.id, 0);
			}
		}

		/// <summary>
		/// 清理所有引用数为0的资源以释放内存空间。
		/// </summary>

		public virtual void UnloadUnusedAssets ()
		{
			throw new NotImplementedException ();
		}

		/// <summary>
		/// 获得{id}-{path}映射表文件的路径。
		/// </summary>

        protected virtual string GetMapFilePath ()
        {
            throw new NotImplementedException ();
        }

		/// <summary>
		/// 类应该重写该方法，从磁盘读取资源。成功时调用OnLoadSuccess，失败时调用OnLoadFail。
		/// </summary>

        protected virtual void DoLoad (AssetRequest request)
        {
			throw new NotImplementedException();
        }

		/// <summary>
		/// 子类在<code>DoLoad</code>加载成功后，应调用该方法通知请求者。
		/// </summary>

        protected void OnLoadSuccess (uint assetId, T asset)
        {
			GameAsset<T> newAsset = new GameAsset<T> (assetId, asset);
			m_cachedAssets.Add (assetId, newAsset);

			if (!m_waitingRequests.ContainsKey (assetId))
			{
				throw new Exception (string.Format ("资源加载已成功，但未找到待反馈的请求者：{0}, {1}", assetId, asset));
			}

			IList<AssetRequest> requests = m_waitingRequests[assetId];
			for (int i = 0, len = requests.Count; i < len; ++ i)
			{
				StartCoroutine (Co_NotifyLoadSucc (requests[i], newAsset));
			}
			m_waitingRequests.Remove (assetId);
        }

		/// <summary>
		/// 子类在<code>DoLoad<code>加载失败后，应调用该方法通知请求者。
		/// </summary>

        protected void OnLoadFail (uint assetId, string error)
        {
            // 磁盘上的资源如果加载不成功，那应该是没找到或者系统错误，没必要重试
			if (!m_waitingRequests.ContainsKey (assetId))
			{
				throw new Exception (string.Format ("资源加载失败，未找到待反馈的请求者：{0}", assetId));
			}

			IList<AssetRequest> requests = m_waitingRequests[assetId];
			for (int i = 0, len = requests.Count; i < len; ++ i)
			{
				StartCoroutine (Co_NotifyLoadFail (requests[i], error));
			}
			m_waitingRequests.Remove (assetId);
        }

		/// <summary>
		/// 获取和资源ID绑定的路径（相对于运行时资源目录）。
		/// </summary>

		protected string GetAssetPath (uint assetId)
		{
			if (!m_assetIdPathTable.ContainsKey (assetId))
			{
				throw new Exception (string.Format ("未找到指定Id的资源路径：{0}", assetId));
			}
			return m_assetIdPathTable[assetId];
		}

		/// <summary>
		/// 加载成功后回调请求者。
		/// </summary>

		IEnumerator Co_NotifyLoadSucc (AssetRequest request, GameAsset<T> asset)
		{
			asset.IncRef ();

			if (m_zeroRefAssets.ContainsKey (asset.id))
			{
				m_zeroRefAssets.Remove (asset.id);
			}

			request.SetResultAsset<T> (asset);
			Debug.Log ("增加资源引用计数：" + request.assetUrl);
			yield return null;
			Debug.Log ("通知请求者：" + request.assetUrl);
			request.requester (request);
		}

		/// <summary>
		/// 加载失败后回调请求者。
		/// </summary>

		IEnumerator Co_NotifyLoadFail (AssetRequest request, string error)
		{
			request.error = error;
			yield return null;
			request.requester (request);
		}
        
		/// <summary>
		/// {id}-{path}映射表。
		/// </summary>

        protected IDictionary<uint, string> m_assetIdPathTable = new Dictionary<uint, string> ();

		/// <summary>
		/// 已缓存的资源列表。
		/// </summary>

		protected IDictionary<uint, GameAsset<T>> m_cachedAssets = new Dictionary<uint, GameAsset<T>> ();

		/// <summary>
		/// 引用为0的已缓存资源。
		/// </summary>

		protected IDictionary<uint, int> m_zeroRefAssets = new Dictionary<uint, int> ();

		/// <summary>
		/// 等待加载结果的请求列表，有时候同一资源在未完成加载时会被请求多次，把所有请求放在一个列表
		/// 中，完成时统一回调。
		/// </summary>

		protected IDictionary<uint,IList<AssetRequest>> m_waitingRequests = new Dictionary<uint, IList<AssetRequest>> ();

		/// <summary>
		/// 加载的实际执行者，关注加载并发度、优先级等。
		/// </summary>

		protected GameAssetsLoader m_assetLoader = null;
	}
}